#pragma once
#include "TestApp.h"
#include <d3d12.h>
#include <dxgiformat.h>
#include "Core/Application.h"

const std::string ASSET_PATH = "D:/projects/selahengine/";

void TestApp::populateCommandList() {}

void TestApp::onUpdate(float dt)
{
    frameTime = dt;
    cameraController->update(dt);
    camera.setDirty();

    uint local_idx = frameIndex % APP_NUM_FRAMES_IN_FLIGHT;
    auto& constants = *(reinterpret_cast<Constants*>(constantData) + local_idx);
    auto& local_c = local_constants[local_idx];
    float3 p = camera.getPosition();
    local_c.View = camera.getViewMatrix();
    local_c.ViewProj = camera.getViewProjMatrix();
    local_c.InvViewProj = camera.getInvViewProjMatrix();
    local_c.CameraPosTime = float4(p.x(), p.y(), p.z(), dt);
    local_c.WidthHeight = int4(config.windowDesc.width, config.windowDesc.height, 0, 0);
    local_c.ExposureKd = float4(exposure, Kd, exposure, exposure);


    memcpy(&constants, &local_c,sizeof(Constants));
}

bool TestApp::onKeyboardEvent(const SL::KeyboardEvent& keyEvent)
{
    cameraController->onKeyEvent(keyEvent);
    if (keyEvent.type == SL::KeyboardEvent::Type::KeyPressed)
    {
        m_char = char(keyEvent.key);
    }
    return true;
}

bool TestApp::onMouseEvent(const SL::MouseEvent& mouseEvent)
{
    cameraController->onMouseEvent(mouseEvent);

    if (mouseEvent.type == SL::MouseEvent::Type::Move)
    {
        cur_mouse = mouseEvent.pos;
    }
    return true;
}
void TestApp::onGuiRender()
{
    ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.7f;
    ImGui::Begin("Panel");
    ImGui::Text("moving mouses to [%f,%f]\n", cur_mouse.x(), cur_mouse.y());
    ImGui::Text("Last key pressed: %c\n", m_char);
    ImGui::Text("frameTime: %.3f\n", frameTime);
    ImGui::Text("frameRate: %.3f\n", 1000 / frameTime);
    ImGui::SliderFloat("sens", cameraController->getSensitivity(), 0.5f, 10.f);
    ImGui::Checkbox("vsync", &config.windowDesc.enableVSync);
    ImGui::SliderFloat3("camPos", camera.getPosVec(), -2.0, 2.0f);
    ImGui::SliderFloat("exposure", &exposure, -5.f, 5.f);
    // ImGui::SliderFloat("Kd", &Kd, .f, 5.f);
    ImGui::End();
}
void TestApp::onFrameRender()
{
    // Command list allocators can only be reset when the associated
    // command lists have finished execution on the GPU; apps should use
    // fences to determine GPU execution progress.
    frameContext[frameIndex].CommandAllocator->Reset();

    // However, when ExecuteCommandList() is called on a particular command
    // list, that command list can then be reset at any time and must be before
    // re-recording.
    mainCommandList->Reset(frameContext[frameIndex].CommandAllocator, nullptr);

    int width = config.windowDesc.width;
    int height = config.windowDesc.height;

    /*
    set min_depth, max_depth is critical, could result in binary depth
    */
    D3D12_VIEWPORT m_viewport(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height), 0.f, 1.f);
    D3D12_RECT m_scissorRect(0, 0, static_cast<LONG>(width), static_cast<LONG>(height));
    {
        // render mesh
        mainCommandList->SetPipelineState(pipelineStateCube);
        mainCommandList->SetGraphicsRootSignature(rootSignatureCube);
        mainCommandList->SetGraphicsRootConstantBufferView(
            0, constantBuffer->GetGPUVirtualAddress() + sizeof(Constants) * (frameIndex % APP_NUM_FRAMES_IN_FLIGHT)
        );
        // set heaps before handle
        mainCommandList->SetDescriptorHeaps(1, &srvDescHeap);
        mainCommandList->SetGraphicsRootDescriptorTable(1, skyboxHandleGPU);
        mainCommandList->RSSetViewports(1, &m_viewport);
        mainCommandList->RSSetScissorRects(1, &m_scissorRect);

        D3D12_RESOURCE_BARRIER barrier = {
            .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
            .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
            .Transition =
                {.pResource = mainLinearTargetResource[frameIndex],
                 .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
                 .StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
                 .StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET},
        };
        mainCommandList->ResourceBarrier(1, &barrier);

        const float clear_color_with_alpha[4] = {0.45f, 0.55f, 0.60f, 1.00f};
        D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = dsvDescHeap->GetCPUDescriptorHandleForHeapStart();

        mainCommandList->OMSetRenderTargets(1, &mainLinearRenderTargetDescriptor[frameIndex], FALSE, &dsvHandle);
        mainCommandList->ClearRenderTargetView(mainLinearRenderTargetDescriptor[frameIndex], clear_color_with_alpha, 0, nullptr);
        mainCommandList->ClearDepthStencilView(
            dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr
        );

        mainCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        mainCommandList->IASetVertexBuffers(0, 1, &vertexBufferView);
        mainCommandList->IASetIndexBuffer(&indexBufferView);
        mainCommandList->DrawIndexedInstanced(indices.size(), 1, 0, 0, 0);
    }
    {
        // render skybox
        mainCommandList->SetGraphicsRootSignature(rootSignatureSkybox);
        mainCommandList->SetPipelineState(pipelineStateSkybox);
        mainCommandList->SetGraphicsRootConstantBufferView(
            0, constantBuffer->GetGPUVirtualAddress() + sizeof(Constants) * (frameIndex % APP_NUM_FRAMES_IN_FLIGHT)
        );
        mainCommandList->SetDescriptorHeaps(1, &srvDescHeap);
        mainCommandList->SetGraphicsRootDescriptorTable(1, skyboxHandleGPU);

        mainCommandList->DrawInstanced(3, 1, 0, 0);
    }
    {
        // tonemapping
        D3D12_RESOURCE_BARRIER barriers[2] = {
            {
                .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
                .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
                .Transition =
                    {.pResource = mainLinearTargetResource[frameIndex],
                     .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
                     .StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET,
                     .StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE},
            },
            {
                .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
                .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
                .Transition =
                    {.pResource = mainRenderTargetResource[frameIndex],
                     .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
                     .StateBefore = D3D12_RESOURCE_STATE_PRESENT,
                     .StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET},
            }
        };
        mainCommandList->ResourceBarrier(2, barriers);
        mainCommandList->SetGraphicsRootSignature(rootSignatureTonemapping);
        mainCommandList->SetPipelineState(pipelineStateTonemapping);
        mainCommandList->SetDescriptorHeaps(1, &srvDescHeap);
        mainCommandList->SetGraphicsRootDescriptorTable(0, mainLinearHandleGPU[frameIndex]);

        D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = dsvDescHeap->GetCPUDescriptorHandleForHeapStart();

        mainCommandList->OMSetRenderTargets(1, &mainRenderTargetDescriptor[frameIndex], FALSE, &dsvHandle);
        mainCommandList->DrawInstanced(3, 1, 0, 0);
    }

    // populateCommandList();

    renderUI();

    D3D12_RESOURCE_BARRIER barrier = {
        .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
        .Transition =
            {.pResource = mainRenderTargetResource[frameIndex],
             .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
             .StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET,
             .StateAfter = D3D12_RESOURCE_STATE_PRESENT},
    };

    mainCommandList->ResourceBarrier(1, &barrier);
    mainCommandList->Close();
}

void TestApp::onLoad()
{
    // camera.setPosition(float3(0, 0, -3));
    // camera.setTarget(float3(0, 0, 0));
    cameraController = new OrbiterCameraController(&camera);

    // Load assets, initialize resources, etc.
    // This is where you would typically load textures, models, etc.
    prepareGraphicsPipeline();
    // Command lists are created in the recording state, but there is nothing
    // to record yet. The main loop expects it to be closed, so close it now.
    TIMER_BEGIN()
    loadModels();
    TIMER_END("model_loading")

    TIMER_BEGIN()
    loadHDR();
    // loadTextures();
    TIMER_END("texture_loading")

    vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
    vertexBufferView.StrideInBytes = sizeof(Vertex);
    vertexBufferView.SizeInBytes = cubeVertices.size() * sizeof(Vertex);

    indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();
    indexBufferView.SizeInBytes = indices.size() * sizeof(uint32_t);
    indexBufferView.Format = DXGI_FORMAT_R32_UINT;

}
inline HRESULT D3DX12SerializeVersionedRootSignature(
    _In_ const D3D12_VERSIONED_ROOT_SIGNATURE_DESC* pRootSignatureDesc,
    D3D_ROOT_SIGNATURE_VERSION MaxVersion,
    _Outptr_ ID3DBlob** ppBlob,
    _Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorBlob
) noexcept
{
    if (ppErrorBlob != nullptr)
    {
        *ppErrorBlob = nullptr;
    }

    switch (MaxVersion)
    {
    case D3D_ROOT_SIGNATURE_VERSION_1_0:
        switch (pRootSignatureDesc->Version)
        {
        case D3D_ROOT_SIGNATURE_VERSION_1_0:
            return D3D12SerializeRootSignature(&pRootSignatureDesc->Desc_1_0, D3D_ROOT_SIGNATURE_VERSION_1, ppBlob, ppErrorBlob);

        case D3D_ROOT_SIGNATURE_VERSION_1_1:
        {
            HRESULT hr = S_OK;
            const D3D12_ROOT_SIGNATURE_DESC1& desc_1_1 = pRootSignatureDesc->Desc_1_1;

            const SIZE_T ParametersSize = sizeof(D3D12_ROOT_PARAMETER) * desc_1_1.NumParameters;
            void* pParameters = (ParametersSize > 0) ? HeapAlloc(GetProcessHeap(), 0, ParametersSize) : nullptr;
            if (ParametersSize > 0 && pParameters == nullptr)
            {
                hr = E_OUTOFMEMORY;
            }
            auto pParameters_1_0 = static_cast<D3D12_ROOT_PARAMETER*>(pParameters);

            if (SUCCEEDED(hr))
            {
                for (UINT n = 0; n < desc_1_1.NumParameters; n++)
                {
                    __analysis_assume(ParametersSize == sizeof(D3D12_ROOT_PARAMETER) * desc_1_1.NumParameters);
                    pParameters_1_0[n].ParameterType = desc_1_1.pParameters[n].ParameterType;
                    pParameters_1_0[n].ShaderVisibility = desc_1_1.pParameters[n].ShaderVisibility;

                    switch (desc_1_1.pParameters[n].ParameterType)
                    {
                    case D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS:
                        pParameters_1_0[n].Constants.Num32BitValues = desc_1_1.pParameters[n].Constants.Num32BitValues;
                        pParameters_1_0[n].Constants.RegisterSpace = desc_1_1.pParameters[n].Constants.RegisterSpace;
                        pParameters_1_0[n].Constants.ShaderRegister = desc_1_1.pParameters[n].Constants.ShaderRegister;
                        break;

                    case D3D12_ROOT_PARAMETER_TYPE_CBV:
                    case D3D12_ROOT_PARAMETER_TYPE_SRV:
                    case D3D12_ROOT_PARAMETER_TYPE_UAV:
                        pParameters_1_0[n].Descriptor.RegisterSpace = desc_1_1.pParameters[n].Descriptor.RegisterSpace;
                        pParameters_1_0[n].Descriptor.ShaderRegister = desc_1_1.pParameters[n].Descriptor.ShaderRegister;
                        break;

                    case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE:
                        const D3D12_ROOT_DESCRIPTOR_TABLE1& table_1_1 = desc_1_1.pParameters[n].DescriptorTable;

                        const SIZE_T DescriptorRangesSize = sizeof(D3D12_DESCRIPTOR_RANGE) * table_1_1.NumDescriptorRanges;
                        void* pDescriptorRanges =
                            (DescriptorRangesSize > 0 && SUCCEEDED(hr)) ? HeapAlloc(GetProcessHeap(), 0, DescriptorRangesSize) : nullptr;
                        if (DescriptorRangesSize > 0 && pDescriptorRanges == nullptr)
                        {
                            hr = E_OUTOFMEMORY;
                        }
                        auto pDescriptorRanges_1_0 = static_cast<D3D12_DESCRIPTOR_RANGE*>(pDescriptorRanges);

                        if (SUCCEEDED(hr))
                        {
                            for (UINT x = 0; x < table_1_1.NumDescriptorRanges; x++)
                            {
                                __analysis_assume(DescriptorRangesSize == sizeof(D3D12_DESCRIPTOR_RANGE) * table_1_1.NumDescriptorRanges);
                                pDescriptorRanges_1_0[x].BaseShaderRegister = table_1_1.pDescriptorRanges[x].BaseShaderRegister;
                                pDescriptorRanges_1_0[x].NumDescriptors = table_1_1.pDescriptorRanges[x].NumDescriptors;
                                pDescriptorRanges_1_0[x].OffsetInDescriptorsFromTableStart =
                                    table_1_1.pDescriptorRanges[x].OffsetInDescriptorsFromTableStart;
                                pDescriptorRanges_1_0[x].RangeType = table_1_1.pDescriptorRanges[x].RangeType;
                                pDescriptorRanges_1_0[x].RegisterSpace = table_1_1.pDescriptorRanges[x].RegisterSpace;
                            }
                        }

                        D3D12_ROOT_DESCRIPTOR_TABLE& table_1_0 = pParameters_1_0[n].DescriptorTable;
                        table_1_0.NumDescriptorRanges = table_1_1.NumDescriptorRanges;
                        table_1_0.pDescriptorRanges = pDescriptorRanges_1_0;
                    }
                }
            }

            if (SUCCEEDED(hr))
            {
                D3D12_ROOT_SIGNATURE_DESC desc_1_0(
                    desc_1_1.NumParameters, pParameters_1_0, desc_1_1.NumStaticSamplers, desc_1_1.pStaticSamplers, desc_1_1.Flags
                );
                hr = D3D12SerializeRootSignature(&desc_1_0, D3D_ROOT_SIGNATURE_VERSION_1, ppBlob, ppErrorBlob);
            }

            if (pParameters)
            {
                for (UINT n = 0; n < desc_1_1.NumParameters; n++)
                {
                    if (desc_1_1.pParameters[n].ParameterType == D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE)
                    {
                        auto pDescriptorRanges_1_0 = pParameters_1_0[n].DescriptorTable.pDescriptorRanges;
                        HeapFree(GetProcessHeap(), 0, reinterpret_cast<void*>(const_cast<D3D12_DESCRIPTOR_RANGE*>(pDescriptorRanges_1_0)));
                    }
                }
                HeapFree(GetProcessHeap(), 0, pParameters);
            }
            return hr;
        }
        }
        break;

    case D3D_ROOT_SIGNATURE_VERSION_1_1:
        return D3D12SerializeVersionedRootSignature(pRootSignatureDesc, ppBlob, ppErrorBlob);
    }

    return E_INVALIDARG;
}

void TestApp::prepareGraphicsPipeline()
{
    // create depth stencil
    {
        D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc = {
            .Format = DXGI_FORMAT_D32_FLOAT,
            .ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D,
            .Flags = D3D12_DSV_FLAG_NONE,
        };

        D3D12_CLEAR_VALUE depthOptimizedClearValue = {.Format = DXGI_FORMAT_D32_FLOAT, .DepthStencil = {1.0f, 0}};

        const D3D12_HEAP_PROPERTIES depthStencilHeapProps = {
            .Type = D3D12_HEAP_TYPE_DEFAULT,
            .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
            .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
            .CreationNodeMask = 1,
            .VisibleNodeMask = 1
        };

        D3D12_RESOURCE_DESC depthStencilDesc = {
            .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
            .Alignment = 0,
            .Width = config.windowDesc.width,
            .Height = config.windowDesc.height,
            .DepthOrArraySize = 1,
            .MipLevels = 0,
            .Format = DXGI_FORMAT_D32_FLOAT,
            .SampleDesc = {1, 0},
            .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
            .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
        };

        device->CreateCommittedResource(
            &depthStencilHeapProps,
            D3D12_HEAP_FLAG_NONE,
            &depthStencilDesc,
            D3D12_RESOURCE_STATE_DEPTH_WRITE,
            &depthOptimizedClearValue,
            IID_PPV_ARGS(&depthStencil)
        );

        device->CreateDepthStencilView(depthStencil, &depthStencilViewDesc, dsvDescHeap->GetCPUDescriptorHandleForHeapStart());
    }

    // Create the constant buffer.
    {
        const uint64_t constantBufferSize = sizeof(Constants) * APP_NUM_FRAMES_IN_FLIGHT;

        const D3D12_HEAP_PROPERTIES constantBufferHeapProps = {
            .Type = D3D12_HEAP_TYPE_UPLOAD,
            .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
            .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
            .CreationNodeMask = 1,
            .VisibleNodeMask = 1
        };
        const D3D12_RESOURCE_DESC constantBufferDesc = {
            .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
            .Alignment = 0,
            .Width = constantBufferSize,
            .Height = 1,
            .DepthOrArraySize = 1,
            .MipLevels = 1,
            .Format = DXGI_FORMAT_UNKNOWN,
            .SampleDesc = {1, 0},
            .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
            .Flags = D3D12_RESOURCE_FLAG_NONE
        };

        device->CreateCommittedResource(
            &constantBufferHeapProps,
            D3D12_HEAP_FLAG_NONE,
            &constantBufferDesc,
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&constantBuffer)
        );

        // Describe and create a constant buffer view.
        D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {
            .BufferLocation = constantBuffer->GetGPUVirtualAddress(), .SizeInBytes = constantBufferSize
        };

        // Map and initialize the constant buffer. We don't unmap this until the
        // app closes. Keeping things mapped for the lifetime of the resource is okay.
        constantBuffer->Map(0, nullptr, &constantData);
    }
    // Create root signature.
    {
        D3D12_STATIC_SAMPLER_DESC sampler = {};
        sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
        sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
        sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
        sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
        sampler.MipLODBias = 0;
        sampler.MaxAnisotropy = 0;
        sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
        sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
        sampler.MinLOD = 0.0f;
        sampler.MaxLOD = D3D12_FLOAT32_MAX;
        sampler.ShaderRegister = 0;
        sampler.RegisterSpace = 0;
        sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

        D3D12_DESCRIPTOR_RANGE1 ranges[2] = {
            // skybox tex
            {.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
             .NumDescriptors = 1,
             .BaseShaderRegister = 0,
             .RegisterSpace = 0,
             .Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC,
             .OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND},
            // static sampler
            {.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
             .NumDescriptors = 1,
             .BaseShaderRegister = 0,
             .RegisterSpace = 0,
             .Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE,
             .OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND},
        };
        D3D12_ROOT_PARAMETER1 rootParam[] = {
            {.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, .Descriptor = {0, 0}, .ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL},

            {.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
             .DescriptorTable = {1, &ranges[0]},
             .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL},
            // {.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
            //  .DescriptorTable = {1, &ranges[1]},
            //  .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL}
        };

        D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc = {
            .Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
            .Desc_1_1 =
                {.NumParameters = 2,
                 .pParameters = rootParam,
                 .NumStaticSamplers = 1,
                 .pStaticSamplers = &sampler,
                 .Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE}
        };
        {
            // skybox signature
            ID3DBlob* signature;
            ID3DBlob* error;
            D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_1, &signature, &error);
            if (error)
                std::cout << ((char*)error->GetBufferPointer()) << std::endl;
            device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignatureSkybox));
            signature->Release();
            if (error)
                error->Release();
        }
        {
            // cube signature
            rootSignatureDesc = {
                .Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
                .Desc_1_1 =
                    {.NumParameters = 2,
                     .pParameters = rootParam,
                     .NumStaticSamplers = 1,
                     .pStaticSamplers = &sampler,
                     .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT}
            };

            ID3DBlob* signature;
            ID3DBlob* error;
            D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_1, &signature, &error);
            device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignatureCube));
            signature->Release();

            if (error)
                error->Release();
        }
        { // tonemapp signature

        D3D12_DESCRIPTOR_RANGE1 ranges[] = {
            // skybox tex
            {.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
             .NumDescriptors = 1,
             .BaseShaderRegister = 0,
             .RegisterSpace = 0,
             .Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC,
             .OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND},
        };
        D3D12_ROOT_PARAMETER1 rootParam[] = {
            {.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
             .DescriptorTable = {1, &ranges[0]},
             .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL},
        };
            // tonemap signature
            rootSignatureDesc = {
                .Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
                .Desc_1_1 =
                    {.NumParameters = 1,
                     .pParameters = rootParam,
                     .NumStaticSamplers = 1,
                     .pStaticSamplers = &sampler,
                     .Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE}
            };

            ID3DBlob* signature;
            ID3DBlob* error;
            D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_1, &signature, &error);
            device->CreateRootSignature(
                0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignatureTonemapping)
            );
            signature->Release();

            if (error)
                error->Release();
        }
    }
    // Create the pipeline state, which includes compiling and loading shaders.
    {
        {
            // basic mesh
            ID3DBlob* vertexShader =
                ShaderReflector::getInstance().compileShaderToObj(L"Shaders/cube.hlsl", ShaderReflector::ShaderType::Vertex);
            ID3DBlob* pixelShader =
                ShaderReflector::getInstance().compileShaderToObj(L"Shaders/cube.hlsl", ShaderReflector::ShaderType::Pixel);
            D3D12_SHADER_BYTECODE vs = {
                .pShaderBytecode = vertexShader->GetBufferPointer(), .BytecodeLength = vertexShader->GetBufferSize()
            };
            D3D12_SHADER_BYTECODE ps = {.pShaderBytecode = pixelShader->GetBufferPointer(), .BytecodeLength = pixelShader->GetBufferSize()};

            // Define the vertex input layout.
            D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = {
                {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
                {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
                {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},

            };
            D3D12_RASTERIZER_DESC rasterizerDesc = {
                .FillMode = D3D12_FILL_MODE_SOLID,
                .CullMode = D3D12_CULL_MODE_BACK,
                .FrontCounterClockwise = TRUE,
                .DepthBias = D3D12_DEFAULT_DEPTH_BIAS,
                .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
                .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
                .DepthClipEnable = TRUE,
                .MultisampleEnable = FALSE,
                .AntialiasedLineEnable = FALSE,
                .ForcedSampleCount = 0,
                .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
            };

            D3D12_BLEND_DESC blendDesc = {.AlphaToCoverageEnable = FALSE, .IndependentBlendEnable = FALSE};
            const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = {
                FALSE,
                FALSE,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_LOGIC_OP_NOOP,
                D3D12_COLOR_WRITE_ENABLE_ALL,
            };
            for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
                blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc;

            D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {
                .DepthEnable = TRUE,
                .DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL,
                .DepthFunc = D3D12_COMPARISON_FUNC_LESS,
                .StencilEnable = FALSE,
                // .StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK,
                // .StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK,
                // .FrontFace = {D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS},
                // .BackFace = {D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS}
            };


            // Describe and create the graphics pipeline state object (PSO).
            D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
            psoDesc.InputLayout = {inputElementDescs, _countof(inputElementDescs)};
            psoDesc.pRootSignature = rootSignatureCube;
            psoDesc.VS = vs;
            psoDesc.PS = ps;
            psoDesc.RasterizerState = rasterizerDesc;
            psoDesc.BlendState = blendDesc;
            psoDesc.DepthStencilState = depthStencilDesc;
            psoDesc.SampleMask = UINT_MAX;
            psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
            psoDesc.NumRenderTargets = 1;
            psoDesc.RTVFormats[0] = DXGI_FORMAT_R16G16B16A16_FLOAT;
            psoDesc.DSVFormat = depthStencil->GetDesc().Format;
            psoDesc.SampleDesc.Count = 1;
            HRESULT hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineStateCube));
            if (hr != S_OK)
            {
                std::cout << "Failed to create graphics pipeline state: " << std::hex << hr << std::endl;
            }
            vertexShader->Release();
            pixelShader->Release();
        }

        {
            // skybox
            ID3DBlob* vertexShader =
                ShaderReflector::getInstance().compileShaderToObj(L"Shaders/skybox.hlsl", ShaderReflector::ShaderType::Vertex);
            ID3DBlob* pixelShader =
                ShaderReflector::getInstance().compileShaderToObj(L"Shaders/skybox.hlsl", ShaderReflector::ShaderType::Pixel);
            D3D12_SHADER_BYTECODE vs = {
                .pShaderBytecode = vertexShader->GetBufferPointer(), .BytecodeLength = vertexShader->GetBufferSize()
            };

            D3D12_SHADER_BYTECODE ps = {.pShaderBytecode = pixelShader->GetBufferPointer(), .BytecodeLength = pixelShader->GetBufferSize()};

            D3D12_RASTERIZER_DESC rasterizerDesc = {
                .FillMode = D3D12_FILL_MODE_SOLID,
                .CullMode = D3D12_CULL_MODE_BACK,
                .FrontCounterClockwise = TRUE,
                .DepthBias = D3D12_DEFAULT_DEPTH_BIAS,
                .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
                .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
                .DepthClipEnable = TRUE,
                .MultisampleEnable = FALSE,
                .AntialiasedLineEnable = FALSE,
                .ForcedSampleCount = 0,
                .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
            };

            D3D12_BLEND_DESC blendDesc = {.AlphaToCoverageEnable = FALSE, .IndependentBlendEnable = FALSE};
            const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = {
                FALSE,
                FALSE,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_LOGIC_OP_NOOP,
                D3D12_COLOR_WRITE_ENABLE_ALL,
            };
            for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
                blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc;

            D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {
                .DepthEnable = TRUE,
                .DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO,
                .DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL,
                .StencilEnable = FALSE,
            };

            // Describe and create the graphics pipeline state object (PSO).
            D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
            psoDesc.InputLayout = {};
            psoDesc.pRootSignature = rootSignatureSkybox;
            psoDesc.VS = vs;
            psoDesc.PS = ps;
            psoDesc.RasterizerState = rasterizerDesc;
            psoDesc.BlendState = blendDesc;
            psoDesc.DepthStencilState = depthStencilDesc;
            psoDesc.SampleMask = UINT_MAX;
            psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
            psoDesc.NumRenderTargets = 1;
            psoDesc.RTVFormats[0] = DXGI_FORMAT_R16G16B16A16_FLOAT;
            psoDesc.DSVFormat = depthStencil->GetDesc().Format;
            psoDesc.SampleDesc.Count = 1;
            HRESULT hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineStateSkybox));
            if (hr != S_OK)
            {
                std::cout << "Failed to create graphics pipeline state: " << std::hex << hr << std::endl;
            }
            vertexShader->Release();
            pixelShader->Release();
        }
        {
            // tonemapping
            ID3DBlob* vertexShader =
                ShaderReflector::getInstance().compileShaderToObj(L"Shaders/tonemapping.hlsl", ShaderReflector::ShaderType::Vertex);
            ID3DBlob* pixelShader =
                ShaderReflector::getInstance().compileShaderToObj(L"Shaders/tonemapping.hlsl", ShaderReflector::ShaderType::Pixel);
            D3D12_SHADER_BYTECODE vs = {
                .pShaderBytecode = vertexShader->GetBufferPointer(), .BytecodeLength = vertexShader->GetBufferSize()
            };

            D3D12_SHADER_BYTECODE ps = {.pShaderBytecode = pixelShader->GetBufferPointer(), .BytecodeLength = pixelShader->GetBufferSize()};

            D3D12_RASTERIZER_DESC rasterizerDesc = {
                .FillMode = D3D12_FILL_MODE_SOLID,
                .CullMode = D3D12_CULL_MODE_BACK,
                .FrontCounterClockwise = TRUE,
                .DepthBias = D3D12_DEFAULT_DEPTH_BIAS,
                .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
                .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
                .DepthClipEnable = TRUE,
                .MultisampleEnable = FALSE,
                .AntialiasedLineEnable = FALSE,
                .ForcedSampleCount = 0,
                .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
            };

            D3D12_BLEND_DESC blendDesc = {.AlphaToCoverageEnable = FALSE, .IndependentBlendEnable = FALSE};
            const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = {
                FALSE,
                FALSE,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_LOGIC_OP_NOOP,
                D3D12_COLOR_WRITE_ENABLE_ALL,
            };
            for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
                blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc;

            D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {
                .DepthEnable = FALSE,
                .DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO,
                .DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS,
                .StencilEnable = FALSE,
            };

            // Describe and create the graphics pipeline state object (PSO).
            D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
            psoDesc.InputLayout = {};
            psoDesc.pRootSignature = rootSignatureTonemapping;
            psoDesc.VS = vs;
            psoDesc.PS = ps;
            psoDesc.RasterizerState = rasterizerDesc;
            psoDesc.BlendState = blendDesc;
            psoDesc.DepthStencilState = depthStencilDesc;
            psoDesc.SampleMask = UINT_MAX;
            psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
            psoDesc.NumRenderTargets = 1;
            psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
            psoDesc.DSVFormat = depthStencil->GetDesc().Format;
            psoDesc.SampleDesc.Count = 1;
            HRESULT hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineStateTonemapping));
            if (hr != S_OK)
            {
                std::cout << "Failed to create graphics pipeline state: " << std::hex << hr << std::endl;
            }
            vertexShader->Release();
            pixelShader->Release();
        }
    }
}

void TestApp::onShutdown()
{
    delete cameraController;

    // Cleanup resources, release memory, etc.
    srvDescHeapAlloc.Free(skyboxHandleCPU, skyboxHandleGPU);
    if (rootSignatureTonemapping)
    {
        rootSignatureTonemapping->Release();
    }
    if (pipelineStateTonemapping)
    {
        pipelineStateTonemapping->Release();
    }
    if (constantBuffer)
    {
        constantBuffer->Unmap(0, nullptr);
        constantBuffer->Release();
        constantBuffer = nullptr;
    }
    if (skyboxTexture)
    {
        skyboxTexture->Release();
        skyboxTexture = nullptr;
    }
    if (vertexBuffer)
    {
        vertexBuffer->Release();
        vertexBuffer = nullptr;
    }
    if (indexBuffer)
    {
        indexBuffer->Release();
        indexBuffer = nullptr;
    }
    if (uploadBuffer)
    {
        uploadBuffer->Release();
        // uploadBuffer = nullptr;
    }
    if (pipelineStateCube)
    {
        pipelineStateCube->Release();
        pipelineStateCube = nullptr;
    }
    if (rootSignatureCube)
    {
        rootSignatureCube->Release();
        rootSignatureCube = nullptr;
    }

    if (pipelineStateSkybox)
    {
        pipelineStateSkybox->Release();
        pipelineStateSkybox = nullptr;
    }
    if (rootSignatureSkybox)
    {
        rootSignatureSkybox->Release();
        rootSignatureSkybox = nullptr;
    }
}

void TestApp::loadModels()
{
    OBJLoader loader;
    loader.loadOBJ(ASSET_PATH + "Assets/Models/bunny.obj");
    cubeVertices = loader.getVertexBuffer();
    indices = loader.getIndexBuffer();

    uploadResourceBuffer(
        cubeVertices.data(), cubeVertices.size() * sizeof(Vertex), &vertexBuffer, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER
    );
    uploadResourceBuffer(indices.data(), indices.size() * sizeof(uint32_t), &indexBuffer, D3D12_RESOURCE_STATE_INDEX_BUFFER);
}
void TestApp::loadHDR()
{
    TextureLoader loader;

    if(config.hdrPath.empty())
        config.hdrPath = ASSET_PATH + "Assets/HDR/qwantani_sunset_4k.hdr";
        // config.hdrPath = "D:/projects/selahengine/Assets/HDR/irradiance_map.hdr";

    int width, height, channels;
    float* pData = loader.loadHDR(config.hdrPath.c_str(), &width, &height, &channels);

    D3D12_SUBRESOURCE_DATA subresources{.pData = pData, .RowPitch = width * channels * 4, .SlicePitch = height * width * channels * 4};
    uploadResourceHDR(width, height, &skyboxTexture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, 1, &subresources);

    srvDescHeapAlloc.Alloc(&skyboxHandleCPU, &skyboxHandleGPU);

    // Describe and create a SRV for the texture.
    D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {
        .Format = DXGI_FORMAT_R32G32B32_FLOAT,
        .ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D,
        .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
        .Texture2D = {.MostDetailedMip = 0, .MipLevels = 1, .PlaneSlice = 0, .ResourceMinLODClamp = 0}
    };
    device->CreateShaderResourceView(skyboxTexture, &srvDesc, skyboxHandleCPU);
}

void TestApp::loadTextures()
{
    // void* ptr = nullptr;
    // std::unique_ptr<uint8_t[]> ddsData;
    // std::vector<D3D12_SUBRESOURCE_DATA> subresources;
    // DirectX::LoadDDSTextureFromFile(device, "", &skyboxTexture, ddsData, subresources)
    TextureLoader loader;
    std::string str_face[6] = {"px", "nx", "py", "ny", "pz", "nz"};
    std::string filePath = "D:/projects/selahengine/Assets/HDR/qwantani_sunset_4k_png/";
    // std::string filePath = "D:/projects/selahengine/Assets/HDR/mirrored_hall_4k_png/";
    std::vector<unsigned char*> faces(6);

    UINT faceWidth, faceHeight;
    for (int i = 0; i < 6; i++)
    {
        int width, height, channels;
        unsigned char* faceData = loader.loadTexture((filePath + str_face[i] + ".png").c_str(), &width, &height, &channels, 4);
        faceWidth = width;
        faceHeight = height;
        faces[i] = new unsigned char[faceWidth * faceHeight * 4]; // RGBA
        memcpy(faces[i], faceData, faceWidth * faceHeight * 4);
        loader.freeTexture(faceData);
    }
    D3D12_SUBRESOURCE_DATA subresources[6];
    for (int i = 0; i < 6; i++)
    {
        subresources[i].pData = faces[i];
        subresources[i].RowPitch = faceWidth * 4; // RGBA = 4 bytes
        subresources[i].SlicePitch = faceWidth * faceHeight * 4;
    }
    uploadResourceTexture(faceWidth, faceHeight, &skyboxTexture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, 6, subresources);

    for (int i = 0; i < 6; i++)
    {
        delete faces[i];
    }

    srvDescHeapAlloc.Alloc(&skyboxHandleCPU, &skyboxHandleGPU);

    // Describe and create a SRV for the texture.
    D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc =
        {.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
         .ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE,
         .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
         .TextureCube = {
             .MostDetailedMip = 0,
             .MipLevels = 1,
             .ResourceMinLODClamp = 0.0f,
         }};
    device->CreateShaderResourceView(skyboxTexture, &srvDesc, skyboxHandleCPU);
}

void TestApp::uploadResourceBuffer(const void* pData, size_t dataSize, ID3D12Resource** ppResource, D3D12_RESOURCE_STATES afterState)
{ // Create GPU buffer
    D3D12_HEAP_PROPERTIES heapProps = {
        .Type = D3D12_HEAP_TYPE_DEFAULT,
        .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
        .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
        .CreationNodeMask = 1,
        .VisibleNodeMask = 1
    };
    D3D12_RESOURCE_DESC resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
        .Alignment = 0,
        .Width = dataSize,
        .Height = 1,
        .DepthOrArraySize = 1,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_UNKNOWN,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(ppResource)
    );

    // Create upload buffer
    heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)
    );

    // Copy data
    void* mappedData;
    D3D12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
    uploadBuffer->Map(0, nullptr, &mappedData);
    memcpy(mappedData, pData, dataSize);
    uploadBuffer->Unmap(0, nullptr);

    mainCommandList->Reset(frameContext[frameIndex].CommandAllocator, nullptr);
    // Schedule GPU copy
    mainCommandList->CopyBufferRegion(*ppResource, 0, uploadBuffer, 0, dataSize);

    // Transition to vertex buffer
    D3D12_RESOURCE_BARRIER barrier = {
        .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
        .Transition =
            {.pResource = *ppResource,
             .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
             .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
             .StateAfter = afterState},
    };

    mainCommandList->ResourceBarrier(1, &barrier);

    mainCommandList->Close();
    ID3D12CommandList* ppCommandLists[] = {mainCommandList};
    directQueue->ExecuteCommandLists(1, ppCommandLists);

    // waitForLastSubmittedFrame();
    waitForGpu();

    uploadBuffer->Release();
    uploadBuffer = nullptr;
}

void MemcpySubresource(
    _In_ const D3D12_MEMCPY_DEST* pDest,
    _In_ const D3D12_SUBRESOURCE_DATA* pSrc,
    SIZE_T RowSizeInBytes,
    UINT NumRows,
    UINT NumSlices
) noexcept
{
    for (UINT z = 0; z < NumSlices; ++z)
    {
        auto pDestSlice = static_cast<BYTE*>(pDest->pData) + pDest->SlicePitch * z;
        auto pSrcSlice = static_cast<const BYTE*>(pSrc->pData) + pSrc->SlicePitch * LONG_PTR(z);
        for (UINT y = 0; y < NumRows; ++y)
        {
            memcpy(pDestSlice + pDest->RowPitch * y, pSrcSlice + pSrc->RowPitch * LONG_PTR(y), RowSizeInBytes);
        }
    }
}

inline UINT64 UpdateSubresources(
    _In_ ID3D12GraphicsCommandList* pCmdList,
    _In_ ID3D12Resource* pDestinationResource,
    _In_ ID3D12Resource* pIntermediate,
    _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
    _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources,
    UINT64 RequiredSize,
    _In_reads_(NumSubresources) const D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts,
    _In_reads_(NumSubresources) const UINT* pNumRows,
    _In_reads_(NumSubresources) const UINT64* pRowSizesInBytes,
    _In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData
) noexcept
{
    // Minor validation
    auto IntermediateDesc = pIntermediate->GetDesc();
    auto DestinationDesc = pDestinationResource->GetDesc();
    if (IntermediateDesc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER || IntermediateDesc.Width < RequiredSize + pLayouts[0].Offset ||
        RequiredSize > SIZE_T(-1) ||
        (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER && (FirstSubresource != 0 || NumSubresources != 1)))
    {
        return 0;
    }

    BYTE* pData;
    HRESULT hr = pIntermediate->Map(0, nullptr, reinterpret_cast<void**>(&pData));
    if (FAILED(hr))
    {
        return 0;
    }

    for (UINT i = 0; i < NumSubresources; ++i)
    {
        if (pRowSizesInBytes[i] > SIZE_T(-1))
            return 0;
        D3D12_MEMCPY_DEST DestData = {
            pData + pLayouts[i].Offset, pLayouts[i].Footprint.RowPitch, SIZE_T(pLayouts[i].Footprint.RowPitch) * SIZE_T(pNumRows[i])
        };
        MemcpySubresource(&DestData, &pSrcData[i], static_cast<SIZE_T>(pRowSizesInBytes[i]), pNumRows[i], pLayouts[i].Footprint.Depth);
    }
    pIntermediate->Unmap(0, nullptr);

    if (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)
    {
        pCmdList->CopyBufferRegion(pDestinationResource, 0, pIntermediate, pLayouts[0].Offset, pLayouts[0].Footprint.Width);
    }
    else
    {
        for (UINT i = 0; i < NumSubresources; ++i)
        {
            D3D12_TEXTURE_COPY_LOCATION Dst = {
                .pResource = pDestinationResource,
                .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
                .SubresourceIndex = i + FirstSubresource
            };
            D3D12_TEXTURE_COPY_LOCATION Src = {
                .pResource = pIntermediate,
                .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
                .PlacedFootprint = pLayouts[i],
            };
            pCmdList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);
        }
    }
    return RequiredSize;
}

UINT64 UpdateSubresources(
    _In_ ID3D12GraphicsCommandList* pCmdList,
    _In_ ID3D12Resource* pDestinationResource,
    _In_ ID3D12Resource* pIntermediate,
    UINT64 IntermediateOffset,
    _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
    _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources,
    _In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData
) noexcept
{
    UINT64 RequiredSize = 0;
    auto MemToAlloc = static_cast<UINT64>(sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + sizeof(UINT) + sizeof(UINT64)) * NumSubresources;
    if (MemToAlloc > SIZE_MAX)
    {
        return 0;
    }
    void* pMem = HeapAlloc(GetProcessHeap(), 0, static_cast<SIZE_T>(MemToAlloc));
    if (pMem == nullptr)
    {
        return 0;
    }
    auto pLayouts = static_cast<D3D12_PLACED_SUBRESOURCE_FOOTPRINT*>(pMem);
    auto pRowSizesInBytes = reinterpret_cast<UINT64*>(pLayouts + NumSubresources);
    auto pNumRows = reinterpret_cast<UINT*>(pRowSizesInBytes + NumSubresources);

    auto Desc = pDestinationResource->GetDesc();
    ID3D12Device* pDevice = nullptr;
    pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast<void**>(&pDevice));
    pDevice->GetCopyableFootprints(
        &Desc, FirstSubresource, NumSubresources, IntermediateOffset, pLayouts, pNumRows, pRowSizesInBytes, &RequiredSize
    );
    pDevice->Release();

    UINT64 Result = UpdateSubresources(
        pCmdList,
        pDestinationResource,
        pIntermediate,
        FirstSubresource,
        NumSubresources,
        RequiredSize,
        pLayouts,
        pNumRows,
        pRowSizesInBytes,
        pSrcData
    );
    HeapFree(GetProcessHeap(), 0, pMem);
    return Result;
}

inline UINT64 GetRequiredIntermediateSize(
    _In_ ID3D12Resource* pDestinationResource,
    _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
    _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources
) noexcept
{
    auto Desc = pDestinationResource->GetDesc();
    UINT64 RequiredSize = 0;

    ID3D12Device* pDevice = nullptr;
    pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast<void**>(&pDevice));
    pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, 0, nullptr, nullptr, nullptr, &RequiredSize);
    pDevice->Release();

    return RequiredSize;
}

void TestApp::uploadResourceTexture(

    UINT width,
    UINT height,
    ID3D12Resource** ppResource,
    D3D12_RESOURCE_STATES afterState,
    UINT NumSubresources,
    const D3D12_SUBRESOURCE_DATA* pSrcData
)
{
    // Create GPU buffer
    D3D12_HEAP_PROPERTIES heapProps = {
        .Type = D3D12_HEAP_TYPE_DEFAULT,
        .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
        .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
        .CreationNodeMask = 1,
        .VisibleNodeMask = 1
    };
    D3D12_RESOURCE_DESC resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
        .Alignment = 0,
        .Width = width,
        .Height = height,
        .DepthOrArraySize = (UINT16)NumSubresources,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(ppResource)
    );
    const UINT64 uploadBufferSize = GetRequiredIntermediateSize(*ppResource, 0, NumSubresources);

    // Create upload buffer
    heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
    resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
        .Alignment = 0,
        .Width = uploadBufferSize,
        .Height = 1,
        .DepthOrArraySize = 1,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_UNKNOWN,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)
    );

    mainCommandList->Reset(frameContext[frameIndex].CommandAllocator, nullptr);

    UpdateSubresources(mainCommandList, *ppResource, uploadBuffer, 0, 0, NumSubresources, pSrcData);

    // Transition to vertex buffer
    D3D12_RESOURCE_BARRIER barrier = {
        .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
        .Transition =
            {.pResource = *ppResource,
             .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
             .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
             .StateAfter = afterState},
    };

    mainCommandList->ResourceBarrier(1, &barrier);

    mainCommandList->Close();
    ID3D12CommandList* ppCommandLists[] = {mainCommandList};
    directQueue->ExecuteCommandLists(1, ppCommandLists);

    waitForGpu();

    uploadBuffer->Release();
    uploadBuffer = nullptr;
}

void TestApp::uploadResourceHDR(
    UINT width,
    UINT height,
    ID3D12Resource** ppResource,
    D3D12_RESOURCE_STATES afterState,
    UINT NumSubresources,
    const D3D12_SUBRESOURCE_DATA* pSrcData
)
{
    // Create GPU buffer
    D3D12_HEAP_PROPERTIES heapProps = {
        .Type = D3D12_HEAP_TYPE_DEFAULT,
        .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
        .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
        .CreationNodeMask = 1,
        .VisibleNodeMask = 1
    };
    D3D12_RESOURCE_DESC resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
        .Alignment = 0,
        .Width = width,
        .Height = height,
        .DepthOrArraySize = (UINT16)NumSubresources,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_R32G32B32_FLOAT,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(ppResource)
    );
    const UINT64 uploadBufferSize = GetRequiredIntermediateSize(*ppResource, 0, NumSubresources);

    // Create upload buffer
    heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
    resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
        .Alignment = 0,
        .Width = uploadBufferSize,
        .Height = 1,
        .DepthOrArraySize = 1,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_UNKNOWN,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)
    );

    mainCommandList->Reset(frameContext[frameIndex].CommandAllocator, nullptr);

    UpdateSubresources(mainCommandList, *ppResource, uploadBuffer, 0, 0, NumSubresources, pSrcData);

    // Transition to vertex buffer
    D3D12_RESOURCE_BARRIER barrier = {
        .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
        .Transition =
            {.pResource = *ppResource,
             .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
             .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
             .StateAfter = afterState},
    };

    mainCommandList->ResourceBarrier(1, &barrier);

    mainCommandList->Close();
    ID3D12CommandList* ppCommandLists[] = {mainCommandList};
    directQueue->ExecuteCommandLists(1, ppCommandLists);

    waitForGpu();

    uploadBuffer->Release();
    uploadBuffer = nullptr;
}

